package system

import (
	"context"
	"fmt"
	"time"

	"admin_template/bff/admin/hertz_gen/base"
	genSysUser "admin_template/bff/admin/hertz_gen/system/sys_user"
	"admin_template/bff/admin/internal/base/data"
	"admin_template/bff/admin/internal/entity/ent"
	"admin_template/bff/admin/internal/entity/ent/predicate"
	"admin_template/bff/admin/internal/entity/ent/syscompany"
	"admin_template/bff/admin/internal/entity/ent/sysrole"
	"admin_template/bff/admin/internal/entity/ent/sysuser"
	"admin_template/bff/admin/internal/service/system"

	"gitee.com/byx_darwin/go-tools/kitex/rpc_error"
	toolsCrypto "gitee.com/byx_darwin/go-tools/tools/crypto"
	toolsTime "gitee.com/byx_darwin/go-tools/tools/time"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/google/uuid"
	"github.com/pkg/errors"
)

type userRepo struct {
	*data.Data
	db *ent.SysUserClient
}

func NewUserRepo(d *data.Data) system.UserRepo {
	return &userRepo{
		Data: d,
		db:   d.DB.SysUser,
	}
}

func (repo *userRepo) Add(ctx context.Context, req any) error {
	info, ok := req.(*genSysUser.AddRequest)
	if !ok {
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysUser.AddRequest"))
	}
	userUUID, err := uuid.NewV7()
	if err != nil {
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			err)
	}
	pwd := toolsCrypto.EncodePwd(info.UserPassword, repo.Config.ServerOption.Auth.SK)
	err = repo.db.
		Create().
		SetUserName(info.UserName).
		SetNickName(info.UserNickName).
		SetAvatar(info.UserAvatar).
		SetEmail(info.UserEmail).
		SetPhone(info.UserMobile).
		SetRemark(info.UserRemark).
		SetPassword(pwd).
		SetCompanyID(int(info.CompanyId)).
		SetUUID(userUUID.String()).
		SetRoleID(int(info.RoleId)).Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys user create(user_name:%s )",
			info.UserName)
		if ent.IsConstraintError(err) {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return nil
}
func (repo *userRepo) UpdateInfo(ctx context.Context,
	req any) error {
	info, ok := req.(*genSysUser.UpdateRequest)
	if !ok {
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysUser.UpdateRequest"))
	}
	err := repo.db.
		UpdateOneID(int(info.Id)).
		SetUserName(info.UserName).
		SetNickName(info.UserNickName).
		SetAvatar(info.UserAvatar).
		SetEmail(info.UserEmail).
		SetPhone(info.UserMobile).
		SetRemark(info.UserRemark).
		SetCompanyID(int(info.CompanyId)).
		SetRoleID(int(info.RoleId)).Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys user udpate(user_name:%s )",
			info.UserName)
		if ent.IsConstraintError(err) {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return nil
}

func (repo *userRepo) List(ctx context.Context, req any) (int64, int64, int64, []any, error) {
	info, ok := req.(*genSysUser.ListRequest)
	if !ok {
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysApi.ListRequest"))
	}
	predicates := make([]predicate.SysUser, 0)
	switch info.State {
	case 1:
		predicates = append(predicates, sysuser.DeleteTimeNotNil())
	case 2:
		predicates = append(predicates, sysuser.DeleteTimeIsNil())
	}

	sql := repo.db.Query()
	if len(predicates) > 0 {
		sql = sql.Where(predicates...)
	}
	total, err := sql.Count(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("分页查询用户总数失败: "))
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	if total == 0 {
		return 0, 0, 0, nil, nil
	}
	offset := (info.Page - 1) * info.Size
	list, err := repo.sqlSelect().Where(predicates...).
		Offset(int(offset)).
		Limit(int(info.Size)).Order(ent.Asc(sysuser.FieldID)).All(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("分页查询用户列表失败: "))
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	userList := make([]any, 0, len(list))
	for _, value := range list {
		userList = append(userList, repo.genInfo(value))
	}
	return int64(total), info.Page, info.Size, userList, nil
}

func (repo *userRepo) All(ctx context.Context,
	req any) ([]any, error) {
	info, ok := req.(*genSysUser.AllRequest)
	if !ok {
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysApi.AllRequest"))
	}

	predicates := make([]predicate.SysUser, 0)
	switch info.State {
	case 1:
		predicates = append(predicates, sysuser.DeleteTimeNotNil())
	case 2:
		predicates = append(predicates, sysuser.DeleteTimeIsNil())
	}

	list, err := repo.sqlSelect().Where(predicates...).Order(ent.Asc(sysuser.FieldID)).All(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("查询全部用户列表失败: "))
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	userList := make([]any, 0, len(list))
	for _, value := range list {
		userList = append(userList, repo.genInfo(value))
	}
	return userList, nil

}

func (repo *userRepo) StateUpdate(ctx context.Context,
	info *base.StateUpdateRequest) error {
	switch info.State {
	case 0:
		err := repo.db.UpdateOneID(int(info.ID)).SetDeleteTime(time.Now()).Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("更新用户状态失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlUpdateFailure, err)
		}
	case 1:
		err := repo.db.UpdateOneID(int(info.ID)).ClearDeleteTime().Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("更新用户状态失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlUpdateFailure, err)
		}
	case 2:
		err := repo.db.DeleteOneID(int(info.ID)).Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("删除用户失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlDeleteFailure, err)
		}
	}
	return nil
}

func (repo *userRepo) UpdatePwd(ctx context.Context,
	c *app.RequestContext, info *genSysUser.UpdatePasswordRequest) error {
	oldPwd := toolsCrypto.EncodePwd(info.UserPassword, repo.Config.ServerOption.Auth.SK)
	newPwd := toolsCrypto.EncodePwd(info.NewPassword, repo.Config.ServerOption.Auth.SK)
	userUUID := repo.UserUUID(c)
	err := repo.db.Update().
		SetPassword(newPwd).
		Where(sysuser.And(sysuser.Password(oldPwd),
			sysuser.UUID(userUUID))).Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys user update pwd(uuid:%s)",
			userUUID)
		if ent.IsNotFound(err) {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return nil
}

func (repo *userRepo) ResetPwd(ctx context.Context, userID string) error {
	pwd := toolsCrypto.EncodePwd(toolsCrypto.MD5([]byte(repo.Config.SystemConfig.DefaultPassword)),
		repo.Config.ServerOption.Auth.SK)
	err := repo.db.Update().SetPassword(pwd).Where(sysuser.UUID(userID)).Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys user update pwd(user_ID:%s)",
			userID)
		err = errors.Wrap(err, msg)
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
	}
	return nil
}

func (repo *userRepo) Auth(ctx context.Context,
	userName,
	password string) (*genSysUser.Info, error) {
	password = toolsCrypto.EncodePwd(password, repo.Config.ServerOption.Auth.SK)
	info, err := repo.sqlSelect().Where(sysuser.And(sysuser.UserName(userName),
		sysuser.Password(password))).First(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys user auth (username:%s)",
			userName)
		if ent.IsNotFound(err) {
			err = errors.Wrap(err, msg)
			return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return repo.genInfo(info), nil
}

func (repo *userRepo) One(ctx context.Context,
	id int) (any, error) {
	info, err := repo.sqlSelect().Where(sysuser.ID(id)).First(ctx)
	if err != nil {
		if ent.IsNotFound(err) {
			return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataInvalid, err)
		}
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	return repo.genInfo(info), nil
}

func (repo *userRepo) OneByUUID(ctx context.Context,
	userUUID string) (*genSysUser.Info, error) {
	info, err := repo.sqlSelect().Where(sysuser.UUID(userUUID)).First(ctx)
	if err != nil {
		if ent.IsNotFound(err) {
			return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataInvalid, err)
		}
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	return repo.genInfo(info), nil
}

func (repo *userRepo) sqlSelect() *ent.SysUserSelect {
	params := []string{
		sysuser.FieldUUID,
		sysuser.FieldUserName,
		sysuser.FieldNickName,
		sysuser.FieldAvatar,
		sysuser.FieldEmail,
		sysuser.FieldPhone,
		sysuser.FieldRemark,
		sysuser.FieldCreateTime,
		sysuser.FieldUpdateTime,
		sysuser.FieldDeleteTime,
		sysuser.FieldAddress,
	}
	return repo.db.Query().WithRole(func(query *ent.SysRoleQuery) {
		query.Select(sysrole.FieldID,
			sysrole.FieldCode,
			sysrole.FieldName)
	}).WithCompany(func(query *ent.SysCompanyQuery) {
		query.Select(syscompany.FieldID, syscompany.FieldName)
	}).Select(params...)
}

func (repo *userRepo) genInfo(info *ent.SysUser) *genSysUser.Info {
	state := int32(1)
	if info.DeleteTime.Unix() > 0 {
		state = 0
	}
	return &genSysUser.Info{
		Id:           int64(info.ID),
		UserUuid:     info.UUID,
		UserName:     info.UserName,
		UserNickName: info.NickName,
		UserAvatar:   info.Avatar,
		UserEmail:    info.Email,
		UserMobile:   info.Phone,
		CreateTime:   toolsTime.Format(info.CreateTime.Unix(), "YYYY-MM-DD HH:mm:ss", ""),
		UpdateTime:   toolsTime.Format(info.UpdateTime.Unix(), "YYYY-MM-DD HH:mm:ss", ""),
		CompanyId:    int64(info.Edges.Company.ID),
		CompanyName:  info.Edges.Company.Name,
		State:        state,
		Address:      info.Address,
		RoleName:     info.Edges.Role.Name,
		RoleCode:     info.Edges.Role.Code,
		RoleId:       int64(info.Edges.Role.ID),
		UserRemark:   info.Remark,
	}
}

func (repo *userRepo) Init(ctx context.Context) error {
	info := &genSysUser.AddRequest{
		UserName:     "admin",
		UserNickName: "超级管理员",
		UserEmail:    "18675866967@163.c0m",
		UserMobile:   "18675866967",
		UserRemark:   "管理员",
		UserPassword: toolsCrypto.MD5([]byte(repo.Config.SystemConfig.DefaultPassword)),
		CompanyId:    1,
		RoleId:       1,
		UserAddress:  "深圳宝安新桥",
	}
	return repo.Add(ctx, info)
}
